﻿using EasyModbus.Exceptions;
using System;
using System.IO.Ports;
using System.Net.Sockets;
using System.Text;

namespace Zhaoxi.ModbusLesson
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");

            // 临时在Nuget库上找一个库
            //ModbusLib();

            //ModbusTCP();

            //ModbusRTU();

            ModbusAscii();

            Console.ReadLine();
        }

        // 第三方通信，很多   看选择哪个，不管哪个   基本需要理解Modbus通信
        private static void ModbusLib()
        {
            // 依赖于System.IO.Ports
            // ModbusTCP   IP  Port
            EasyModbus.ModbusClient modbusClient = new EasyModbus.ModbusClient();
            modbusClient.Connect("127.0.0.1", 502);// 连接

            // 一次请求
            modbusClient.UnitIdentifier = 10;

            // 03功能码
            int[] values = modbusClient.ReadHoldingRegisters(1, 4);

            // 知识点：

        }


        public static void ModbusTCP()
        {
            /*
             *  0x00 0x01   Transaction ID
                0x00 0x00   Modbus  Protocol  ID
                0x00 0x06   后续字节数
                0x0A          单元标识符（从站地址）
                0x03           功能码，告诉设备要操作哪个存储区：保持型寄存器
                0x00 0x01   起始地址
                0x00 0x04   读取数量
             */

            // 以太网   TCP协议 
            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socket.Connect("127.0.0.1", 502);

            // 需求：请求Modbus设备中保持型 寄存器的1号地址，连续请求4个
            byte[] buffer = new byte[] {
                0x00,0x01,
                0x00,0x00,
                0x00,0x06,
                0x0A,
                0x03,
                0x00,0x01,
                0x00,0x04
            };

            socket.Send(buffer);

            byte[] bytes = new byte[6];
            socket.Receive(bytes);

            byte[] lenBytes = bytes.ToList().GetRange(4, 2).ToArray();

            // 16位   2Byte
            Array.Reverse(lenBytes);/// 颠倒字节序
            ushort len = BitConverter.ToUInt16(lenBytes, 0);

            // 字节序问题：BitConverter   小端     高位在后   低位在前
            // 返回的字节    高位在前   低位在后



            byte[] bytes1 = new byte[len];
            socket.Receive(bytes1);//这一步者完整的一次通信响应


            // 解析
            // 做响应判断    1、无法响应（超时）     2、有响应，响应异常
            var dataBytes = bytes1.ToList().GetRange(3, 8);
            //for (int i = 0; i < dataBytes.Count; i += 2)
            //{
            //    var db = new byte[] { dataBytes[i + 1], dataBytes[i] };
            //    var v = BitConverter.ToUInt16(db, 0);
            //}

            //怎么知道应该是有符号数，还是无符号数？   取决  设备   通信手册   点位表    
            // 数据的范围    -1000  ~   1000
            // 数据的偏移    +200    -500
            // 数据的系数    0.01

            // 45.34,不能在一个寄存器中保存，需要占用两上寄存器4个字节      2Byte    65535

            // 类型：float    32bit   
            for (int i = 0; i < dataBytes.Count; i += 4)
            {
                var db = new byte[] { dataBytes[i + 3], dataBytes[i + 2], dataBytes[i + 1], dataBytes[i] };
                var v = BitConverter.ToSingle(db, 0);
            }
        }

        public static void ModbusRTU()
        {
            //SerialPort.GetPortNames();

            SerialPort serialPort = new SerialPort();
            serialPort.BaudRate = 9600;
            serialPort.DataBits = 8;
            serialPort.Parity = Parity.None;
            serialPort.StopBits = StopBits.One;
            serialPort.PortName = "COM1";

            serialPort.Open();

            // 可以进行串口通信     
            byte[] buffer = new byte[] {
                0x0A, // 从站地址
                0x03, // 功能码
                0x00,0x01,  // 起始地址
                0x00,0x04   // 读取数量
            };
            // 计算校验码   CRC16
            var crcBytes = CRC16(buffer);


            var list = buffer.ToList();
            list.AddRange(crcBytes);

            //while (true)
            serialPort.Write(list.ToArray(), 0, list.Count); //  发送给设备，设备方进行校验检查

            //Task.Delay(100).GetAwaiter().GetResult();

            byte[] dataBytes = new byte[serialPort.BytesToRead];   // 可以通过计算   得到响应的报文长度
            serialPort.Read(dataBytes, 0, dataBytes.Length);

            // 解析之前，校验检查
            // 最后两个字节  

            var bytes = dataBytes.ToList().GetRange(3, 8);
            for (int i = 0; i < bytes.Count; i += 4)
            {
                var db = new byte[] { bytes[i + 3], bytes[i + 2], bytes[i + 1], bytes[i] };
                var v = BitConverter.ToSingle(db, 0);
            }


            //serialPort.ReadByte();
        }

        static byte[] CRC16(byte[] value, ushort poly = 0xA001, ushort crcInit = 0xFFFF)
        {
            if (value == null || !value.Any())
                throw new ArgumentException("");

            //运算
            ushort crc = crcInit;
            for (int i = 0; i < value.Length; i++)
            {
                crc = (ushort)(crc ^ (value[i]));
                for (int j = 0; j < 8; j++)
                {
                    crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ poly) : (ushort)(crc >> 1);
                }
            }
            //byte hi = (byte)((crc & 0xFF00) >> 8);  //高位置
            //byte lo = (byte)(crc & 0x00FF);         //低位置

            return BitConverter.GetBytes(crc);
        }

        //上位机有没有接收完成中断机制呢，这样就不用延时了，毕竟延时不准，延时会随着波特率调整变化

        private static void ModbusAscii()
        {
            byte[] buffer = new byte[] {
                0x0A, // 从站地址
                0x03, // 功能码
                0x00,0x01,  // 起始地址
                0x00,0x04   // 读取数量
            };

            var strs = string.Join("", buffer.Select(b => b.ToString("X2")));
            var asciiBytes = Encoding.ASCII.GetBytes(strs);
        }
    }
}